#-*- coding: utf-8 -*-
from evaluate import evalAssetsReturn
from helpers import *
from evolution import *
from operators import *
from collections import namedtuple
import observers 
import random
import strategies

#-*- coding: utf-8 -*-

class Evolution(object): # {{{
    """
        Klasa bazowa do przeprowadzania ewolucji
    """

    evolutionObservers = []
    resultObservers    = []

    def __init__(self, function, iterations, chromosomeLen):
        self.iterations         = iterations
        self.function           = function
        self.evolutionObservers = Evolution.evolutionObservers
        self.resultObservers    = Evolution.resultObservers

        self.chromosomeLen = chromosomeLen
        #populationSize - w klasach dziedziczących

    def __call__(self, *args):
        pass
        #algorytm do implementacji w klasach dziedziczących

    def attachResultObserver(self, observer):
        if not observer in self.resultObservers:
            self.resultObservers.append(observer)

    def notifyResultObservers(self, *data):
        if data != None:
            for observer in self.resultObservers:
                observer(*data)

    def attachEvolutionObserver(self, observer):
        if not observer in self.evolutionObservers:
            self.evolutionObservers.append(observer)

    def notifyEvolutionObservers(self, *data):
        if data != None:
            for observer in self.evolutionObservers:
                observer(*data)
# }}}
CgaParams = namedtuple('CgaParams', 'iterations, chromosomeLen, theta')
class Cga(Evolution): # {{{

    """
        Compact Genetic Algorithm
    """

    def __init__(self, function, iterations, chromosomeLen, theta):
        self.theta = theta
        Evolution.__init__(self, function, iterations, chromosomeLen)

    def __call__(self):

        random.seed()

        pbb = initialProbabilityVector(self.chromosomeLen)

        i = 0
        while i < self.iterations:
            x1 = randomIndividual(pbb)
            x2 = randomIndividual(pbb)

            res1 = self.function(x1)
            res2 = self.function(x2)
    
            if res1 > res2:
                best = x1
                worst = x2
            else:
                best = x2
                worst = x1

            for k in range(self.chromosomeLen):
                if best[k] == 1 and worst[k] == 0:
                    pbb[k] += self.theta
                elif best[k] == 0 and worst[k] == 1:
                    pbb[k] -= self.theta

            i += 1

            # zwracamy numer generacji i wartość dostosowania najlepszego osobnika
            self.notifyEvolutionObservers(i, max(res1, res2))

        x1 = randomIndividual(pbb)
        self.notifyResultObservers(x1)
        return x1

# }}}

PbilParams = namedtuple('PbilParams', 'iterations, populationSize, chromosomeLen, theta1, theta2, theta3')
class Pbil(Evolution): # {{{

    """
        Population-Based Incremental Learning
    """

    def __init__(self, function, iterations, populationSize, chromosomeLen, theta1, theta2, theta3):
        self.theta1 = theta1
        self.theta2 = theta2
        self.theta3 = theta3
        self.populationSize = populationSize
        Evolution.__init__(self, function, iterations, chromosomeLen)

    def __call__(self):

        random.seed()

        pbb = initialProbabilityVector(self.chromosomeLen)
        i = 0
        while i < self.iterations:
            population = randomPopulation(pbb, self.populationSize)
            (best, bestVal, evaluated) = populationEval(population, self.function)
    
            for k in range(self.chromosomeLen):
                pbb[k] = pbb[k] * (1 - self.theta1) + best[k] * self.theta1

            for k in range(self.chromosomeLen):
                if random.random() < self.theta2:
                    pbb[k] = pbb[k] * (1 - self.theta3) + binaryRandom(0.5) * self.theta3

            i += 1
            self.notifyEvolutionObservers(i, bestVal)

        population = randomPopulation(pbb, self.populationSize)
        (best, _, _) = populationEval(population, self.function)

        self.notifyResultObservers(best) 
        return best
# }}}

SsgaParams = namedtuple('SsgaParams', 'iterations, selection, crossover, mutation, populationSize, chromosomeLen, blockSize')
class Ssga(Evolution): # {{{
    
    """
        Simplified Simple Genetic Algorithm
    """

    def __init__(self, function, iterations, selection, crossover, mutation, populationSize, chromosomeLen, blockSize, thetaM):
        self.selection = selection
        self.crossover = crossover
        self.mutation = mutation
        self.populationSize = populationSize
        self.blockSize = blockSize
        self.thetaM = thetaM
        Evolution.__init__(self, function, iterations, chromosomeLen)

    def __call__(self):
        random.seed()
        pbb = initialProbabilityVector(self.chromosomeLen)
        population = randomPopulation(pbb, self.populationSize)
        best, bestVal, evaluatedPopulation = populationEval(population, self.function)
        
        i = 0
        while i < self.iterations:
            evaluatedPopulation = self.selection(evaluatedPopulation, self.blockSize)#blockSelection(evaluatedPopulation, self.blockSize)
            population = self.crossover(evaluatedPopulation)

            population = self.mutation(population, self.thetaM)

            best, bestVal, evaluatedPopulation = populationEval(population, self.function)

            self.notifyEvolutionObservers(i, bestVal)

            i += 1

        self.notifyResultObservers(best)
        return best

# }}}

# vim: fdm=marker
